<?php
// +-----------------------------------------------------------------------------------------------
// | 简易CMS
// +-----------------------------------------------------------------------------------------------
// | [请手动修改文件描述]
// +-----------------------------------------------------------------------------------------------
// | Author: IT果农 <htmambo@163.com> <http://www.haolie.net>
// +-----------------------------------------------------------------------------------------------
// | Version $Id: Sftp.class.php 9 2016-09-17 11:22:17Z IT果农 <htmambo@163.com> $
// +-----------------------------------------------------------------------------------------------

namespace CMS;

class SFTP {

    private $host;
    private $user;
    private $pass;
    private $port;
    private $rootpath;
    private $conn = false;
    private $sftp = false;
    private $error;
    private $stream;
    private $stream_timeout = 100;
    private $log;
    private $lastLog;

    public function __construct($host, $user, $pass, $port, $rootpath) {
        $this->host = $host;
        $this->user = $user;
        $this->pass = $pass;
        $this->port = $port;
        $this->rootpath = $rootpath;
        if ($this->connect()->authenticate()) {
            return true;
        }
    }

    public function isConnected() {
        return (boolean) $this->conn;
    }

    public function __get($name) {
        return $this->$name;
    }

    public function connect() {
        $this->logAction("Connecting to {$this->host}");
        if ($this->conn = ssh2_connect($this->host, $this->port)) {
            $this->logAction("Connecting to {$this->host} successed");
            return $this;
        }
        $this->logAction("Connection to {$this->host} failed");
        throw new Exception("Unable to connect to {$this->host}");
    }

    public function authenticate() {
        $this->logAction("Authenticating to {$this->host}");
        if (ssh2_auth_password($this->conn, $this->user, $this->pass)) {
            $this->sftp = @ssh2_sftp($this->conn);
            if (!$this->sftp)
                throw new Exception("Could not initialize SFTP subsystem.");
            return $this;
        }
        $this->logAction("Authentication to {$this->host} failed");
        throw new Exception("Unable to authenticate to {$this->host}");
    }

    public function makeDir($remotePath) {
        $sftp = $this->sftp;
        $stat = @ssh2_sftp_lstat($sftp, $this->rootpath . $remotePath);
        if ($stat) {
            return true;
        }
        $this->logAction('mkDir ' . $this->rootpath . $remotePath);
        $result = ssh2_sftp_mkdir($sftp, $this->rootpath . $remotePath, 0777, true);
        return $result;
    }

    public function sendFile($localFile, $remoteFile, $permision = 0644) {
        $localFile = ROOT_PATH . $localFile;
        $remotePath = dirname($remoteFile);
        $result = $this->makeDir($remotePath);
        if (!$result)
            throw new Exception("Remote path \"$remotePath\" is not found!");
        $remoteFile = $this->rootpath . $remoteFile;
        if (!is_file($localFile))
            return false;
        //throw new Exception("Local file {$localFile} does not exist");
        $this->logAction("Sending file $localFile as $remoteFile");

        $sftp = $this->sftp;
        $stream = @fopen("ssh2.sftp://$sftp$remoteFile", 'w');

        if (!$stream)
            throw new Exception("Could not open file: $remoteFile");

        $data_to_send = @file_get_contents($localFile);
        if ($data_to_send === false)
            throw new Exception("Could not open local file: $localFile.");

        if (@fwrite($stream, $data_to_send) === false)
            throw new Exception("Could not send data from file: $localFile.");

        @fclose($stream);
        $this->logAction("Sending file $localFile as $remoteFile succeeded");
        return true;
    }

    public function getFile($remoteFile, $localFile) {
        $this->logAction("Receiving file $remoteFile as $local_file");
        $remote_file = $this->rootpath . $remoteFile;
        $local_file = ROOT_PATH . $localFile;
        $sftp = $this->sftp;
        $stream = @fopen("ssh2.sftp://$sftp$remote_file", 'r');
        if (!$stream) {
            $this->logAction("Receiving file $remoteFile as $localFile failed");
            throw new Exception("Could not open file: $remoteFile");
        }
        $size = $this->getFileSize($remote_file);
        $contents = '';
        $read = 0;
        $len = $size;
        while ($read < $len && ($buf = fread($stream, $len - $read))) {
            $read += strlen($buf);
            $contents .= $buf;
        }
        file_put_contents($local_file, $contents);
        @fclose($stream);
        $this->logAction("Receiving file $remoteFile as $localFile successed");
    }

    public function getFileSize($file) {
        $sftp = $this->sftp;
        $file = $this->rootpath . $file;
        return @filesize("ssh2.sftp://$sftp$file");
    }

    public function cmd($cmd, $returnOutput = false) {
        $this->logAction("Executing command $cmd");
        $this->stream = ssh2_exec($this->conn, $cmd);

        if (FALSE === $this->stream) {
            $this->logAction("Unable to execute command $cmd");
            throw new Exception("Unable to execute command '$cmd'");
        }
        $this->logAction("$cmd was executed");

        stream_set_blocking($this->stream, true);
        //stream_set_timeout($this->stream, $this->stream_timeout);
        $this->lastLog = stream_get_contents($this->stream);

        $this->logAction("$cmd output: {$this->lastLog}");
        fclose($this->stream);
        $this->log .= $this->lastLog . "\n";
        return ( $returnOutput ) ? $this->lastLog : $this;
    }

    public function shellCmd($cmds = array()) {
        $this->logAction("Openning ssh2 shell");
        $this->shellStream = ssh2_shell($this->conn);

        sleep(1);
        $out = '';
        while ($line = fgets($this->shellStream)) {
            $out .= $line;
        }

        $this->logAction("ssh2 shell output: $out");

        foreach ($cmds as $cmd) {
            $out = '';
            $this->logAction("Writing ssh2 shell command: $cmd");
            fwrite($this->shellStream, "$cmd" . PHP_EOL);
            sleep(1);
            while ($line = fgets($this->shellStream)) {
                $out .= $line;
                sleep(1);
            }
            $this->logAction("ssh2 shell command $cmd output: $out");
        }

        $this->logAction("Closing shell stream");
        fclose($this->shellStream);
    }

    public function getLastOutput() {
        return $this->lastLog;
    }

    public function getOutput() {
        return $this->log;
    }

    public function disconnect() {
        $this->logAction("Disconnecting from {$this->host}");
        // if disconnect function is available call it..
        if (function_exists('ssh2_disconnect')) {
            ssh2_disconnect($this->conn);
        } else { // if no disconnect func is available, close conn, unset var
            @fclose($this->conn);
            $this->conn = false;
        }
        // return null always
        return NULL;
    }

    public function fileExists($path) {
        $path = $this->rootpath . $path;
        $output = $this->cmd("[ls -f $path ] && echo 1 || echo 0", true);
        return (bool) trim($output);
    }

    function logAction($msg = '') {
        $msg = trim($msg);
        if ($msg) {
            \Think\Log::record($msg, 'sftplog');
        }
    }

}
